using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

using VPT = Microsoft.Xna.Framework.Graphics.VertexPositionTexture;

namespace ProjectNoClue.Graphics
{
	/// <summary>
	/// Batches Lines to draw
	/// </summary>
	public class LineBatch
	{
		/// <summary>
		/// Max number of draw
		/// </summary>
		/// <remarks>This number MUST match the shader's</remarks>
		const int MaxLineBatch = 15;

		const int PointOffset = 0;
		const int LSCOffset = MaxLineBatch; //LengthSinCos
		const int LCOffset = MaxLineBatch * 2; //LineColor
		const int WorldOffset = MaxLineBatch * 3; //WorldMatrix
		const int MatrixSize = 4;

		GraphicsDevice graphics;
		Effect effect;
		Matrix proj;

		static VertexBuffer lines = null;
		static IndexBuffer linesIdx = null;
		VertexDeclaration verDec;

		int bufferIdx = 0;

		bool drawing = false;

		/// <summary>
		/// Line Batch constructor
		/// </summary>
		/// <param name="g">Game Class</param>
		/// <remarks>This method calls the Content Manager</remarks>
		public LineBatch(Game g)
		{
			graphics = g.GraphicsDevice;
			effect = g.Content.Load<Effect>("vector");

			verDec = new VertexDeclaration(graphics, VPT.VertexElements);

			CreateLine();

			Resize(null, null);
			g.GraphicsDevice.DeviceReset += new EventHandler(Resize);

		}


		/// <summary>
		/// Start Batching
		/// </summary>
		public void Begin()
		{
			if (drawing)
				throw new InvalidOperationException("Missing End() call");

			drawing = true;

			graphics.RenderState.CullMode = CullMode.None;
			graphics.VertexDeclaration = verDec;
			graphics.Vertices[0].SetSource(lines, 0, VPT.SizeInBytes);
			graphics.Indices = linesIdx;

			effect.Begin();
			effect.CurrentTechnique.Passes[0].Begin();

		}

		/// <summary>
		/// Queue Batch
		/// </summary>
		/// <param name="part">Vector data</param>
		public void Draw(Part part)
		{
			if (!drawing)
				throw new InvalidOperationException("Draw must be in between Begin() and End()");

			Part.Shape[] shapes = part.Shapes;
			Part.Shape.Edge[] edges;


			for (int ii = 0; ii < shapes.Length; ++ii)
			{
				edges = shapes[ii].Edges;
				for (int jj = 0; jj < edges.Length; ++jj)
				{
					graphics.SetVertexShaderConstant(WorldOffset + (bufferIdx * MatrixSize), part.Translation);

					graphics.SetVertexShaderConstant(LCOffset + bufferIdx, shapes[ii].ShapeColor.ToVector4());

					graphics.SetVertexShaderConstant(PointOffset + bufferIdx, edges[jj].Point);
					graphics.SetVertexShaderConstant(LSCOffset + bufferIdx, edges[jj].LenghtSinCos);

					++bufferIdx;

					if (bufferIdx >= MaxLineBatch)
						Flush();
				}

			}

		}

		/// <summary>
		/// Flush Data on draw queue
		/// </summary>
		void Flush()
		{
			if (bufferIdx < 1)
				return;

			graphics.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 12 * bufferIdx, 0, 10 * bufferIdx);
			bufferIdx = 0;
		}

		public void End()
		{
			if (!drawing)
				throw new InvalidOperationException("Missing Begin()");

			Flush();

			effect.CurrentTechnique.Passes[0].End();
			effect.End();

			drawing = false;
		}


		/// <summary>
		/// Creates base mesh to draw lines
		/// </summary>
		void CreateLine()
		{
			if (lines != null)
				return;

			lines = new VertexBuffer(graphics, 12 * MaxLineBatch * VPT.SizeInBytes, BufferUsage.WriteOnly);
			linesIdx = new IndexBuffer(graphics, typeof(short), 30 * MaxLineBatch, BufferUsage.WriteOnly);

			short[] indices = new short[30 * MaxLineBatch];


			int idx = 0;

			for (short jj = 0; jj < MaxLineBatch; ++jj)
			{
				for (short ii = 0; ii < 10; ++ii)
				{
					indices[idx++] = (short)(jj * 12);
					indices[idx++] = (short)((jj * 12) + ii + 1);
					indices[idx++] = (short)((jj * 12) + ii + 2);
				}
			}

			linesIdx.SetData<short>(indices);

			VPT[] data = new VPT[12 * MaxLineBatch];

			data[0] = new VPT(Vector3.Zero, new Vector2(.5f, 0));

			float pie = MathHelper.TwoPi / 8f;
			for (int ii = 0; ii <= 4; ++ii)//First Half of Pill
			{
				data[ii + 1] = new VPT(new Vector3((float)Math.Cos(MathHelper.PiOver2 + pie * ii), (float)Math.Sin(MathHelper.PiOver2 + pie * ii), 0), Vector2.Zero);
			}
			for (int ii = 4; ii <= 8; ++ii)//Second Half of Pill
			{
				data[ii + 2] = new VPT(new Vector3((float)Math.Cos(MathHelper.PiOver2 + pie * ii), (float)Math.Sin(MathHelper.PiOver2 + pie * ii), 0), Vector2.UnitX);
			}
			data[11] = new VPT(new Vector3((float)Math.Cos(MathHelper.PiOver2), (float)Math.Sin(MathHelper.PiOver2), 0), Vector2.Zero);

			idx = 12;
			for (int jj = 1; jj < MaxLineBatch; ++jj)
			{
				for (int ii = 0; ii < 12; ++ii, ++idx)
				{
					data[idx].Position = data[idx - 12].Position;
					data[idx].TextureCoordinate.X = data[idx - 12].TextureCoordinate.X;
					data[idx].TextureCoordinate.Y = jj;
				}
			}

			lines.SetData<VPT>(data);

		}

		/// <summary>
		/// Changes the Projection matrix to fit screen
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void Resize(object sender, EventArgs e)
		{
			PresentationParameters pp = graphics.PresentationParameters;

			Matrix.CreateTranslation(-.5f, .5f, 0, out proj);
			Matrix temp;
			Matrix.CreateOrthographicOffCenter(0, pp.BackBufferWidth, pp.BackBufferHeight, 0, 0, 1, out temp);
			Matrix.Multiply(ref proj, ref temp, out proj);

			effect.Parameters["Project"].SetValue(proj);
		}
	}
}
